Skip to content

fix(contexts): delete the applications of a deleted user - #3196

Open
rubenvdlinde wants to merge 1 commit into
mainfrom
fix/noid/delete-applications-of-deleted-user
Open

rubenvdlinde wants to merge 1 commit into
mainfrom
fix/noid/delete-applications-of-deleted-user

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Nov 2, 2026 •

Copy link
Copy Markdown

When a user was deleted, Tables removed their tables but not their applications. The application stayed in the database with its pages, its shares and the navigation entries of the people it was shared with, owned by a user who no longer exists.

Size: 195 lines: 38 code and 157 tests.

What changes

  • UserDeletedListener now deletes the applications the user owns before it deletes their tables. Applications the user only received through a share are left alone, and so are applications owned by a group. Each application goes through ContextService::delete(), the same path as deleting it in the UI, so its node relations, pages, page content, shares and navigation entries go with it. Applications go first, so deleting the tables doesn't have to detach them from applications that are removed anyway.
  • One application that fails to delete is logged and skipped. The other applications and the tables are still deleted.
  • ShareMapper::findAllSharesForNode() accepts a null sender. The parameter isn't used in the query, but ShareService::deleteAllForContext() passes the session user, which is null when there's no session: occ user:delete, or a user backend removing users in the background. Without this, deleting an application there threw a TypeError that aborted the whole user deletion.

Because the listener goes through ContextService::delete(), anything that method learns to clean up later is also cleaned up when a user is deleted. The widgets and menu items from #3132 and #3135 are both removed in that method, so they need no change here.

How it was tested

  • New tests/unit/Listener/UserDeletedListenerTest.php: only applications the user owns are deleted, applications are deleted before tables, a failing application doesn't stop the rest, and other events are ignored.
  • New test in ShareServiceTest: deleteAllForContext() works without a session user. Without the fix it fails with the TypeError above.
UserDeletedListenerTest without the fix: Tests: 4, Errors: 4 (the listener doesn't take a ContextService)
UserDeletedListenerTest with the fix:    OK (4 tests, 7 assertions)
ShareServiceTest without the mapper fix: TypeError: findAllSharesForNode(): Argument #3 ($sender) must be of type string, null given
ShareServiceTest with the fix:           OK (17 tests, 31 assertions)
Full unit suite, PostgreSQL (Nextcloud 34): OK (427 tests, 843 assertions)
Full unit suite, SQLite (Nextcloud 35):     OK (427 tests, 843 assertions)
composer cs:check: no files to fix
psalm: No errors found!
rector: OK

On a local instance, a user with one table and one application on it. The application is shared with another user, who has it in their navigation. Then the user is deleted. Rows left for that application afterwards, on main and with this change:

Deleted through Application Node relations Pages Page content Shares Navigation Table
provisioning API DELETE /cloud/users/{id} main 1 0 1 0 1 1 0
this change 0 0 0 0 0 0 0
occ user:delete main 1 0 1 0 1 1 0
this change 0 0 0 0 0 0 0

With only the listener change, occ user:delete failed with the TypeError and the user wasn't deleted at all. That is what the ShareMapper change fixes.

🖼️ Screenshots

Backend only, no visual change.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔙 Backport requests are created or not needed: /backport to stableX.X
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

Deleting a user removed their tables but left their applications behind,
with pages, shares and navigation entries, owned by nobody. The user
deleted listener now deletes the applications the user owns through
ContextService::delete(), before their tables. Applications only shared
with the user, or owned by a group, stay.

ShareMapper::findAllSharesForNode() accepts a null sender: it is not
used in the query, but deleting an application without a session user,
as occ user:delete does, passed null and the TypeError aborted the user
deletion.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ruben van der Linde <ruben@conduction.nl>
@rubenvdlinde
rubenvdlinde force-pushed the fix/noid/delete-applications-of-deleted-user branch from 7ec3770 to 11c8005 Compare November 3, 2026 14:51
@rubenvdlinde
rubenvdlinde marked this pull request as ready for review November 6, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants